/*
 * Copyright (c) 2012 Johann Hanne
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "FreeRTOS.h"
#include "semphr.h"

#include "lwip/err.h"
#include "lwip/netif.h"
#include "netif/etharp.h"

#include "ethernet.h"

#include <string.h>
#include <stdint.h>
#include <portio.h>

#define IFNAME0 'S'
#define IFNAME1 'Q'

struct netif sq610eth_if;
static int init_done = 0;

static void low_level_init(struct netif *netif) {
    netif->hwaddr_len = 6;
    ethernet_get_mac_address(netif->hwaddr);
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_ETHARP | NETIF_FLAG_BROADCAST;
}

static err_t low_level_output(struct netif *netif, struct pbuf *pbuf) {
    ethernet_tx_linked_list(pbuf, offsetof(struct pbuf, payload), offsetof(struct pbuf, len), offsetof(struct pbuf, next));
    return ERR_OK;
}

static void low_level_input(uint8_t *data, size_t len) {
    struct pbuf *pb = NULL;
    err_t r;

    pb = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    if (pb == NULL) {
        fprintf(stderr, "pbuf_alloc failed\n");
        return;
    }

    pb->len = len;
    pb->tot_len = len;
    memcpy(pb->payload, data, len);

    r = ethernet_input(pb, &sq610eth_if);
    if (r != ERR_OK) {
        fprintf(stderr, "ethernet_input failed\n");
        return;
    }
}

static xSemaphoreHandle input_semaphore;

void ethernetInputTask(void *pvParameters) {
    struct pbuf *p;
    struct eth_hdr *ethhdr;
    struct netif *netif = (struct netif *)pvParameters;

    xSemaphoreTake(input_semaphore, 0);

    for (;;) {
        if (xSemaphoreTake(input_semaphore, portMAX_DELAY) == pdTRUE) {
            ethernet_rx(low_level_input);
        }
    }
}

void sq610eth_tick(void) {
    if (init_done) {
        xSemaphoreGive(input_semaphore);
    }
}

err_t sq610eth_init(struct netif *netif) {

    if (ethernet_init() != 0) {
        fprintf(stderr, "ethernet_init failed\n");
        return ERR_IF;
    }

    /* We don't support multiple interfaces, so we can set the
       state to NULL... */
    netif->state = NULL;
    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;

    low_level_init(netif);

    etharp_init();

    vSemaphoreCreateBinary(input_semaphore);
    if (input_semaphore == NULL) {
        fprintf(stderr, "vSemaphoreCreateBinary(input_semaphore) failed\n");
    }

    init_done = 1;

    return ERR_OK;
}
